﻿/*
 *  Author: Louis A. Leskoviansky
 *  Company: Artemis Solutions Group (http://www.artemis-solutions.com)
 */


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Web;
using System.Web.UI;

namespace AjaxControlFramework
{
    [ToolboxItem(false), Bindable(false)]
	public partial class AjaxControl : Control, IAjaxControl
	{
		//------// Properties \\--------------------------------------------\\
        [Bindable(false), Browsable(false)]
		public virtual AjaxControlContext AjaxContext { get; set; }

        [Bindable(false), Browsable(false)]
		public virtual bool IsUpdating { get; set; }

        [Bindable(false), Browsable(false)]
        public virtual StateStrategy StateStrategy { get; set; }
        [Bindable(false), Browsable(false)]
		public virtual CallbackStrategy CallbackStrategy { get; set; }


        [Bindable(false), Browsable(false)]
        public delegate IAjaxControlStrategy CreateStrategy(Control control);
        [Bindable(false), Browsable(false)]
        public delegate CallbackSerializer CreateSerializer();
		//------\\ Properties //--------------------------------------------//



		//------// Constructors \\------------------------------------------\\
        public AjaxControl()
        {
            AjaxControl.Initialize(this);
        }
		//------\\ Constructors //------------------------------------------//



		//------// Methods \\-----------------------------------------------\\
		public void RaiseCallbackEvent(string eventArgs)
		{
            CallbackStrategy.ProcessCallback<CallbackStrategyArguments>(eventArgs);
		}


		public string GetCallbackResult()
		{
			return CallbackStrategy.GenerateCallbackResult();
		}


        public static AjaxControlOptionsAttribute GetAjaxControlOptionsAttribute(IAjaxControl ajaxControl)
        {
            //Type baseType = ajaxControl.GetType().BaseType;
            Type baseType = ajaxControl.GetType();
            string optionsCacheKey = GetOptionsAttributeCacheKey(baseType);


            AjaxControlOptionsAttribute optionsAttribute = (AjaxControlOptionsAttribute)HttpContext.Current.Cache[optionsCacheKey];

            if (optionsAttribute == null)
            {
                object[] attributes = baseType.GetCustomAttributes(typeof(AjaxControlOptionsAttribute), true);
                if ((attributes != null) && (attributes.Length > 0))
                {
                    optionsAttribute = (AjaxControlOptionsAttribute)attributes[0];

                    // Cache the AjaxControlOptionsAttribute for the base IAjaxControl type.
                    HttpContext.Current.Cache[optionsCacheKey] = optionsAttribute;
                }
                else
                {
                    // An AjaxControlOptionsAttribute could not be found...so create one with default values and 
                    // store it in the Data Cache.

                    optionsAttribute = new AjaxControlOptionsAttribute();
                    HttpContext.Current.Cache[optionsCacheKey] = optionsAttribute;
                }
            }


            return optionsAttribute;
        }


        /// <summary>The primary purpose of this method is to allow base IAjaxControl implementations to 
        /// ensure all its properties are initialize property so callbacks can be successfully performed 
        /// despite poor configuration by derived implementations. </summary>
        /// <param name="ajaxControl">An IAjaxControl implementation to initialize.</param>
        public static void Initialize(IAjaxControl ajaxControl)
        {
            if (ajaxControl == null) { throw new ArgumentNullException("ajaxControl"); }

            if (ajaxControl.AjaxContext == null) { ajaxControl.AjaxContext = new AjaxControlContext() { Control = ajaxControl as Control }; }

            TemplateControl templateControl = ajaxControl as TemplateControl;
            if (templateControl != null)
            {
                if (templateControl.AppRelativeVirtualPath == null || templateControl.AppRelativeVirtualPath.Trim().Length == 0)
                {
                    throw new InvalidOperationException("IAjaxControl implementations that derive from " + typeof(TemplateControl).FullName + " must provide a valid AppRelativeVirtualPath property value.");
                }
            }

            AjaxControlOptionsAttribute optionsAttribute = GetAjaxControlOptionsAttribute(ajaxControl);
            Control control = (Control)ajaxControl;


            // If a CallbackStrategy has been provided, ensure its sub-components are initialized properly.
            if (ajaxControl.CallbackStrategy != null)
            {
                InitializeScriptStrategy(ajaxControl, optionsAttribute);
                InitializeCallbackSerializer(ajaxControl, optionsAttribute);
            }
            else
            {
                InitializeCallbackStrategy(ajaxControl, optionsAttribute);
                InitializeScriptStrategy(ajaxControl, optionsAttribute);
                InitializeCallbackSerializer(ajaxControl, optionsAttribute);
            }


            if (ajaxControl.StateStrategy == null)
            {
                if (optionsAttribute == null) { optionsAttribute = GetAjaxControlOptionsAttribute(ajaxControl); }

                InitializeStateStrategy(ajaxControl, optionsAttribute);
            }


            // Call the InitializeCallback of the CallbackStrategy object so all that implementors need to do 
            // is make a single call to AjaxControl.InitializeAjaxControl in their constructors.
            ajaxControl.CallbackStrategy.InitializeCallback();
        }


        public static void InitializeCallbackStrategy(IAjaxControl ajaxControl, AjaxControlOptionsAttribute optionsAttribute)
        {
            if (ajaxControl.CallbackStrategy == null)
            {
                if (optionsAttribute.CallbackStrategyType == null)
                {
                    ajaxControl.CallbackStrategy = new ControlCallbackStrategy((Control)ajaxControl);
                    ajaxControl.CallbackStrategy.AutoSaveControlState = optionsAttribute.AutoSaveControlState;
                }
                else
                {
                    if (!optionsAttribute.CallbackStrategyType.IsSubclassOf(typeof(CallbackStrategy)))
                    {
                        throw new ArgumentException(String.Format("The {0} type must be a subclass of {1}.", optionsAttribute.CallbackStrategyType.FullName, typeof(CallbackStrategy).FullName));
                    }

                    CreateStrategy createCallbackStrategy = GetCreateStrategyDelegate(optionsAttribute.CallbackStrategyType);
                    ajaxControl.CallbackStrategy = (CallbackStrategy)createCallbackStrategy((Control)ajaxControl);
                    ajaxControl.CallbackStrategy.AutoSaveControlState = optionsAttribute.AutoSaveControlState;
                }
            }
        }


        public static void InitializeScriptStrategy(IAjaxControl ajaxControl, AjaxControlOptionsAttribute optionsAttribute)
        {
            if (ajaxControl.CallbackStrategy.Scripter == null)
            {
                if (optionsAttribute.ScriptStrategyType == null)
                {
                    ajaxControl.CallbackStrategy.Scripter = new ControlScriptStrategy((Control)ajaxControl);
                    ajaxControl.CallbackStrategy.Scripter.BuildMode = optionsAttribute.BuildMode;
                }
                else
                {
                    if (!optionsAttribute.ScriptStrategyType.IsSubclassOf(typeof(ScriptStrategy)))
                    {
                        throw new ArgumentException(String.Format("The {0} type must be a subclass of {1}.", optionsAttribute.ScriptStrategyType.FullName, typeof(ScriptStrategy).FullName));
                    }

                    CreateStrategy createScriptStrategy = GetCreateStrategyDelegate(optionsAttribute.ScriptStrategyType);
                    ajaxControl.CallbackStrategy.Scripter = (ScriptStrategy)createScriptStrategy((Control)ajaxControl);
                    ajaxControl.CallbackStrategy.Scripter.BuildMode = optionsAttribute.BuildMode;
                }
            }
            else
            {
                ajaxControl.CallbackStrategy.Scripter.BuildMode = optionsAttribute.BuildMode;
            }
        }


        public static void InitializeStateStrategy(IAjaxControl ajaxControl, AjaxControlOptionsAttribute optionsAttribute)
        {
            if (ajaxControl.StateStrategy == null && optionsAttribute.StateStrategyType == null)
            {
                ajaxControl.StateStrategy = new DiskStateStrategy((Control)ajaxControl);
            }
            else
            {
                if (!optionsAttribute.StateStrategyType.IsSubclassOf(typeof(StateStrategy)))
                {
                    throw new ArgumentException(String.Format("The {0} type must be a subclass of {1}.", optionsAttribute.StateStrategyType.FullName, typeof(StateStrategy).FullName));
                }

                CreateStrategy createStateStrategy = GetCreateStrategyDelegate(optionsAttribute.StateStrategyType);
                ajaxControl.StateStrategy = (StateStrategy)createStateStrategy((Control)ajaxControl);
            }
        }


        public static void InitializeCallbackSerializer(IAjaxControl ajaxControl, AjaxControlOptionsAttribute optionsAttribute)
        {
            if (ajaxControl.CallbackStrategy.Serializer == null)
            {
                if (optionsAttribute.CallbackSerializerType == null)
                {
                    ajaxControl.CallbackStrategy.Serializer = new DotNetJsonCallbackSerializer();
                }
                else
                {
                    if (!optionsAttribute.CallbackSerializerType.IsSubclassOf(typeof(CallbackSerializer)))
                    {
                        throw new ArgumentException(String.Format("The {0} type must be a subclass of {1}.", optionsAttribute.CallbackSerializerType.FullName, typeof(CallbackSerializer).FullName));
                    }

                    CreateSerializer createSerializer = GetCreateSerializerDelegate(optionsAttribute.CallbackSerializerType);
                    ajaxControl.CallbackStrategy.Serializer = (CallbackSerializer)createSerializer();
                }
            }
        }


        /// <summary>Gets the key of the AjaxControlOptionsAttribute for an IAjaxControl object stored in the 
        /// ASP.NET Data Cache.</summary>
        /// <param name="baseType">The BaseType property of an IAjaxControl Type object.</param>
        /// <returns>Returns the key of an AjaxControlOptionsAttribute object stored in the ASP.NET Data Cache.</returns>
        public static string GetOptionsAttributeCacheKey(Type baseType)
        {
            return "AjaxControl_" + baseType.FullName + "_OptionsAttribute";
        }


        public static string GetCreateStrategyDelegateCacheKey(Type strategyType)
        {
            return "AjaxControl_" + strategyType.FullName + "_CreateStrategyDelegate";
        }


        public static string GetCreateSerializerDelegateCacheKey(Type serializerType)
        {
            return "AjaxControl_" + serializerType.FullName + "_CreateSerializerDelegate";
        }


        /// <summary></summary>
        /// <param name="strategyType"></param>
        /// <returns></returns>
        public static CreateStrategy GetCreateStrategyDelegate(Type strategyType)
        {
            string createDelegateCacheKey = GetCreateStrategyDelegateCacheKey(strategyType);
            CreateStrategy createDelegate = (CreateStrategy)HttpContext.Current.Cache[createDelegateCacheKey];

            if (createDelegate == null)
            {
                ConstructorInfo constructor = strategyType.GetConstructor(new Type[1] { typeof(Control) });

                if (constructor == null)
                {
                    throw new ArgumentException(String.Format("The {0} type must have a constructor with a single parameter of type System.Web.UI.Control in order to be evaluated properly for the AjaxControlOptions attribute.", strategyType.FullName));
                }


                DynamicMethod dynamicMethod = new DynamicMethod(strategyType.Name, strategyType, new Type[1] { typeof(Control) }, true);

                ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Newobj, constructor);
                ilGenerator.Emit(OpCodes.Ret);

                createDelegate = (CreateStrategy)dynamicMethod.CreateDelegate(typeof(CreateStrategy));
                HttpContext.Current.Cache[createDelegateCacheKey] = createDelegate;
            }


            return createDelegate;
        }


        /// <summary></summary>
        /// <param name="serializerType"></param>
        /// <returns></returns>
        public static CreateSerializer GetCreateSerializerDelegate(Type serializerType)
        {
            string createDelegateCacheKey = GetCreateSerializerDelegateCacheKey(serializerType);
            CreateSerializer createDelegate = (CreateSerializer)HttpContext.Current.Cache[createDelegateCacheKey];

            if (createDelegate == null)
            {
                ConstructorInfo constructor = serializerType.GetConstructor(new Type[0]);

                if (constructor == null)
                {
                    throw new ArgumentException(String.Format("The {0} type must have a public default constructor in order to be evaluated properly for the AjaxControlOptions attribute.", serializerType.FullName));
                }


                DynamicMethod dynamicMethod = new DynamicMethod(serializerType.Name, serializerType, new Type[0], true);

                ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                ilGenerator.Emit(OpCodes.Newobj, constructor);
                ilGenerator.Emit(OpCodes.Ret);

                createDelegate = (CreateSerializer)dynamicMethod.CreateDelegate(typeof(CreateSerializer));
                HttpContext.Current.Cache[createDelegateCacheKey] = createDelegate;
            }


            return createDelegate;
        }
		//------\\ Methods //-----------------------------------------------//
	}
}
